package cheesymock;

import cheesymock.recorder.ArgumentComparator;
import cheesymock.recorder.ArgumentModifier;
import cheesymock.recorder.RecorderAssertionError;
import cheesymock.recorder.RecorderErrorMessageFactory;
import cheesymock.recorder.RecorderMock;
import cheesymock.recorder.RecorderState;

/**
 * Handles recorded expectations for proxy classes.
 * <pre>
 * 		Example mock = Cheesy.mock(Example.class, new Object() {
 *			String name = "foo";
 *		});
 *		// Create expect recorder
 *		Recorder recorder = new Recorder();
 *		Example expect = recorder.expect(mock);
 *		// Setup method expectations
 *		expect.getName();
 *		expect.setName("bar");
 *		expect.getName();
 *		// Do tests
 *		mock.getName(); // returns "foo"
 *		mock.setName("bar");
 *		mock.getName(); // returns "bar"
 *		// Validate that all expectations have been met
 *		recorder.check();
 * </pre>
 * The class is instantiated so that it can create recorder expectations for several proxy classes and
 * still keep track of the order that the different proxies should be invocated.
 *
 * @author Bjorn
 *
 */
public class Recorder {
	protected RecorderErrorMessageFactory messageFactory = new RecorderErrorMessageFactory();
	private RecorderState recorderState = new RecorderState();

	public Recorder() {
	}

	/**
	 * Verify that all expected methods have been run.
	 */
	public void check() {
		checkUninvokedExpectationsMessage();
	}

	public <T> T compare(ArgumentComparator<T> comparator, T expectedValue) {
		recorderState.getArgumentModifiers().add(new ArgumentModifier<T>(expectedValue, comparator));
		return expectedValue;
	}

	/**
	 * Creates an expect recorder for the proxy mock objekt.
	 *
	 * @param <T>
	 * @param proxy
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T expect(T proxy) {
		final Mock handler = (Mock) Cheesy.getProxyFactory().getHandler(proxy);
		RecorderMock mock = new RecorderMock(proxy, recorderState, messageFactory);
		handler.setRecorder(mock);
		final T newProxy = (T) Cheesy.getProxyFactory().newRecorderProxyFromProxy(proxy, mock);
		return newProxy;
	}

	public <T> T ignore(T object) {
		final T expectedArgumentValue = object;
		recorderState.getArgumentModifiers().add(
				new ArgumentModifier<T>(expectedArgumentValue, new ArgumentComparator<T>() {
					public boolean compare(T argument, T comparatorValue) {
						return true;
					}
				}));
		return expectedArgumentValue;
	}

	/**
	 * Ignore a specific argument number (first argument number is 1).
	 *
	 * @param <T>
	 * @param object
	 * @param argumentNumber
	 * @return
	 */
	public <T> T ignore(T object, int argumentNumber) {
		recorderState.getArgumentModifiers().add(new ArgumentModifier<T>(new ArgumentComparator<T>() {
			public boolean compare(T argument, T comparatorValue) {
				return true;
			}
		}, argumentNumber - 1));
		return object;
	}

	public Boolean ignoreBoolean() {
		return ignore(new Boolean(true));
	}

	public Byte ignoreByte() {
		return ignore(new Byte((byte) recorderState.getArgumentComparerIdentifier()));
	}

	public Character ignoreCharacter() {
		return ignore(new Character((char) recorderState.getArgumentComparerIdentifier()));
	}

	public Double ignoreDouble() {
		return ignore(new Double(recorderState.getArgumentComparerIdentifier()));
	}

	public Float ignoreFloat() {
		return ignore(new Float(recorderState.getArgumentComparerIdentifier()));
	}

	public Integer ignoreInteger() {
		return ignore(new Integer(recorderState.getArgumentComparerIdentifier()));
	}

	public Long ignoreLong() {
		return ignore(new Long(recorderState.getArgumentComparerIdentifier()));
	}

	public Short ignoreShort() {
		return ignore(new Short(recorderState.getArgumentComparerIdentifier()));
	}

	public String ignoreString() {
		return ignore(new String("" + recorderState.getArgumentComparerIdentifier()));
	}

	/**
	 * Reset expectations for all mock objects
	 */
	public void reset() {
		recorderState.reset();
	}

	private void checkUninvokedExpectationsMessage() {
		if (recorderState.getExpectedInvocationsQueue().size() > 0)
			throw new RecorderAssertionError(messageFactory.createUninvokedExpectationsMessage(recorderState
					.getCorrectInvocationsQueue(), recorderState.getExpectedInvocationsQueue()));
	}
}
